/**
 * @fileoverview Pane UI component.
 *
 * @author alert83@google.com
 * @see ../demos/pane.html
 */

goog.provide('mp.ui.Pane');
goog.provide('mp.ui.Pane.View');

goog.require('goog.ui.Component.EventType');
goog.require('goog.ui.Container');
goog.require('goog.ui.Container.Orientation');
goog.require('goog.events');
goog.require('goog.events.MouseWheelHandler');
// We need to include following dependency because of the magic with
// goog.ui.registry.setDecoratorByClassName
goog.require('mp.ui.PaneRenderer');
goog.require('goog.ui.registry');



/**
 * @param {mp.ui.Pane.View=} opt_view Pane view; defaults to
 *     {@link mp.ui.Pane.View.TILE}.
 * @param {mp.ui.PaneRenderer=} opt_renderer Renderer used to render or
 *     decorate the container; defaults to {@link mp.ui.PaneRenderer}.
 * @param {goog.dom.DomHelper=} opt_domHelper DOM helper, used for document
 *     interaction.
 * @constructor
 * @extends {goog.ui.Container}
 */
mp.ui.Pane = function(opt_view, opt_renderer, opt_domHelper) {
  goog.ui.Container.call(this, this.getOrientation(),
      opt_renderer || mp.ui.PaneRenderer.getInstance(),
      opt_domHelper);

  this.setView(opt_view || mp.ui.Pane.View.TILE);
};
goog.inherits(mp.ui.Pane, goog.ui.Container);


/**
 * Pane view.
 * @enum {string}
 */
mp.ui.Pane.View = {
  TILE: 'tile',
  LIST: 'list',
  GRID: 'grid'
};


/**
 * Pane view; defaults to {@link mp.ui.Pane.View.TILE}.
 * @type {mp.ui.Pane.View}
 * @private
 */
mp.ui.Pane.prototype.view_;

/**
 * Event handler.
 * @type {goog.events.MouseWheelHandler}
 * @private
 */
mp.ui.Pane.prototype.googMouseWheelHandler_;

/**
 * Whether keyboard navigation should change the selected item, or just move
 * the highlight.  Defaults to true.
 * @type {boolean}
 * @private
 */
mp.ui.Pane.prototype.autoSelectItems_ = true;


/**
 * The currently selected item (null if none).
 * @type {goog.ui.Control?}
 * @private
 */
mp.ui.Pane.prototype.selectedItem_ = null;


/**
 * @override
 */
mp.ui.Pane.prototype.enterDocument = function() {
  mp.ui.Pane.superClass_.enterDocument.call(this);

  this.listenToPaneEvents_();
};

/**
 * @override
 */
mp.ui.Pane.prototype.exitDocument = function() {
  if (this.googMouseWheelHandler_) {
      goog.events.unlisten(this.googMouseWheelHandler_, goog.events.MouseWheelHandler.EventType.MOUSEWHEEL, this.handleMouseWheel);
  }

  mp.ui.Pane.superClass_.exitDocument.call(this);
};

/** @override */
mp.ui.Pane.prototype.disposeInternal = function() {
  mp.ui.Pane.superClass_.disposeInternal.call(this);

  this.selectedItem_ = null;

  if (this.googMouseWheelHandler_) {
    this.googMouseWheelHandler_.dispose();
    delete this.googMouseWheelHandler_;
  }
};


/**
 * Removes the item from the pane.  Overrides the superclass implementation
 * by deselecting the item being removed.  Since {@link #removeChildAt} uses
 * {@link #removeChild} internally, we only need to override this method.
 * @param {string|goog.ui.Component} item Item to remove.
 * @param {boolean=} opt_unrender Whether to call {@code exitDocument} on the
 *     removed item, and detach its DOM from the document (defaults to false).
 * @return {goog.ui.Control} The removed item, if any.
 * @override
 */
mp.ui.Pane.prototype.removeChild = function(item, opt_unrender) {
  // This actually only accepts goog.ui.Controls. There's a TODO
  // on the superclass method to fix this.
  this.deselectIfSelected(/** @type {goog.ui.Control} */ (item));
  return mp.ui.Pane.superClass_.removeChild.call(this, item, opt_unrender);
};


/**
 * @return {mp.ui.Pane.View} Pane view.
 */
mp.ui.Pane.prototype.getView = function() {
  return this.view_;
};


/**
 * Sets the view of the pane.
 * @param {mp.ui.Pane.View} view Pane view.
 * @throws {Error} If the pane has already been rendered.
 */
mp.ui.Pane.prototype.setView = function(view) {
  this.getRenderer().setView(this, view);
  this.view_ = view;
};


/**
 * @return {boolean} Whether keyboard navigation should change the selected item,
 *     or just move the highlight.
 */
mp.ui.Pane.prototype.isAutoSelectItems = function() {
  return this.autoSelectItems_;
};


/**
 * Enables or disables auto-selecting items using the keyboard.  If auto-select
 * is enabled, keyboard navigation switches items immediately, otherwise it just
 * moves the highlight.
 * @param {boolean} enable Whether keyboard navigation should change the
 *     selected item, or just move the highlight.
 */
mp.ui.Pane.prototype.setAutoSelectItems = function(enable) {
  this.autoSelectItems_ = enable;
};


/**
 * Highlights the item at the given index in response to a keyboard event.
 * Overrides the superclass implementation by also selecting the item if
 * {@link #isAutoSelectItems} returns true.
 * @param {number} index Index of item to highlight.
 * @protected
 * @override
 */
mp.ui.Pane.prototype.setHighlightedIndexFromKeyEvent = function(index) {
  mp.ui.Pane.superClass_.setHighlightedIndexFromKeyEvent.call(this, index);
  if (this.autoSelectItems_) {
    // Immediately select the item.
    this.setSelectedItemIndex(index);
  }
};


/**
 * @return {goog.ui.Control?} The currently selected item (null if none).
 */
mp.ui.Pane.prototype.getSelectedItem = function() {
  return this.selectedItem_;
};


/**
 * Selects the given item.
 * @param {goog.ui.Control?} item Item to select (null to select none).
 */
mp.ui.Pane.prototype.setSelectedItem = function(item) {
  if (item) {
    // Select the item and have it dispatch a SELECT event, to be handled in
    // handleItemSelect() below.
    item.setSelected(true);
  } else if (this.getSelectedItem()) {
    // De-select the currently selected item and have it dispatch an UNSELECT
    // event, to be handled in handleItemUnselect() below.
    this.getSelectedItem().setSelected(false);
  }
};


/**
 * @return {number} Index of the currently selected item (-1 if none).
 */
mp.ui.Pane.prototype.getSelectedItemIndex = function() {
  return this.indexOfChild(this.getSelectedItem());
};


/**
 * Selects the item at the given index.
 * @param {number} index Index of the item to select (-1 to select none).
 */
mp.ui.Pane.prototype.setSelectedItemIndex = function(index) {
  this.setSelectedItem(/** @type {goog.ui.Control} */ (this.getChildAt(index)));
};


/**
 * If the specified item is the currently selected item, deselects it, and
 * selects the closest selectable item in the pane (first looking before,
 * then after the deselected item).  Does nothing if the argument is not the
 * currently selected item.  Called internally when a item is removed, hidden,
 * or disabled, to ensure that another item is selected instead.
 * @param {goog.ui.Control?} item Item to deselect (if any).
 * @protected
 */
mp.ui.Pane.prototype.deselectIfSelected = function(item) {
  if (item && item == this.getSelectedItem()) {
    var index = this.indexOfChild(item);
    // First look for the closest selectable item before this one.
    for (var i = index - 1;
         item = /** @type {goog.ui.Control} */ (this.getChildAt(i));
         i--) {
      if (this.isSelectableItem(item)) {
        this.setSelectedItem(item);
        return;
      }
    }
    // Next, look for the closest selectable item after this one.
    for (var j = index + 1;
         item = /** @type {goog.ui.Control} */ (this.getChildAt(j));
         j++) {
      if (this.isSelectableItem(item)) {
        this.setSelectedItem(item);
        return;
      }
    }
    // If all else fails, just set the selection to null.
    this.setSelectedItem(null);
  }
};


/**
 * Returns true if the item is selectable, false otherwise.  Only visible and
 * enabled items are selectable.
 * @param {goog.ui.Control} item Item to check.
 * @return {boolean} Whether the item is selectable.
 * @protected
 */
mp.ui.Pane.prototype.isSelectableItem = function(item) {
  return item.isVisible() && item.isEnabled();
};


/**
 * Handles {@code SELECT} events dispatched by items as they become selected.
 * @param {goog.events.Event} e Select event to handle.
 * @protected
 */
mp.ui.Pane.prototype.handleItemSelect = function(e) {
  if (this.selectedItem_ && this.selectedItem_ != e.target) {
    // Deselect currently selected item.
    this.selectedItem_.setSelected(false);
  }
  this.selectedItem_ = /** @type {goog.ui.Control} */ (e.target);
};


/**
 * Handles {@code UNSELECT} events dispatched by items as they become deselected.
 * @param {goog.events.Event} e Unselect event to handle.
 * @protected
 */
mp.ui.Pane.prototype.handleItemUnselect = function(e) {
  if (e.target == this.selectedItem_) {
    this.selectedItem_ = null;
  }
};


/**
 * Handles {@code DISABLE} events displayed by items.
 * @param {goog.events.Event} e Disable event to handle.
 * @protected
 */
mp.ui.Pane.prototype.handleItemDisable = function(e) {
  this.deselectIfSelected(/** @type {goog.ui.Control} */ (e.target));
};


/**
 * Handles {@code HIDE} events displayed by items.
 * @param {goog.events.Event} e Hide event to handle.
 * @protected
 */
mp.ui.Pane.prototype.handleItemHide = function(e) {
  this.deselectIfSelected(/** @type {goog.ui.Control} */ (e.target));
};


/**
 * Handles focus events dispatched by the pane's key event target.  If no item
 * is currently highlighted, highlights the selected item or the first item if no
 * item is selected either.
 * @param {goog.events.Event} e Focus event to handle.
 * @protected
 * @override
 */
mp.ui.Pane.prototype.handleFocus = function(e) {
  if (!this.getHighlighted()) {
    this.setHighlighted(this.getSelectedItem() ||
        /** @type {goog.ui.Control} */ (this.getChildAt(0)));
  }
};

/**
 * Handles {@code MOUSEWHEEL}.
 * @param {goog.events.MouseWheelEvent} e MouseWheel event to handle.
 * @protected
 */
mp.ui.Pane.prototype.handleMouseWheel = function(e) {
  console.log(e);
};

/**
 * Returns the event handler for this component, lazily created the first time
 * this method is called.
 * @return {!goog.events.MouseWheelHandler} Mouse wheel event handler for this component.
 * @protected
 */
mp.ui.Pane.prototype.getMouseWheelHandler = function() {
  return this.googMouseWheelHandler_ ||
         (this.googMouseWheelHandler_ = new goog.events.MouseWheelHandler(this.getElement ? this.getElement() : this));
};


/**
 * Subscribes to events dispatched by items.
 * @private
 */
mp.ui.Pane.prototype.listenToPaneEvents_ = function() {
  // Listen for SELECT, UNSELECT, DISABLE, and HIDE events dispatched by items.
  this.getHandler().
      listen(this, goog.ui.Component.EventType.SELECT, this.handleItemSelect).
      listen(this, goog.ui.Component.EventType.UNSELECT, this.handleItemUnselect).
      listen(this, goog.ui.Component.EventType.DISABLE, this.handleItemDisable).
      listen(this, goog.ui.Component.EventType.HIDE, this.handleItemHide);

  goog.events.listen(this.getMouseWheelHandler(), goog.events.MouseWheelHandler.EventType.MOUSEWHEEL, this.handleMouseWheel);
};


// Register a decorator factory function for mp.ui.Panes.
goog.ui.registry.setDecoratorByClassName(mp.ui.PaneRenderer.CSS_CLASS,
    function() {
      return new mp.ui.Pane();
    });
